package com.dingwen.treasure.business.controller;

import com.dingwen.treasure.base.controller.BaseViewController;
import com.dingwen.treasure.base.pojo.vo.Result;
import com.dingwen.treasure.model.entity.mybatisplus.SysUser;
import com.dingwen.treasure.mybatisplus.service.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.*;

/**
 * 批量存储控制器
 * TODO JDBC原生批量添加，效率最高
 *
 * @author dingwen
 * @date 2022/05/08
 */
@Api(tags = "批量存储案例")
@RestController
@RequestMapping("batch")
@Slf4j
public class BatchSaveController implements BaseViewController {

    /**
     * asyncExecutor
     */
    @Resource
    private ThreadPoolTaskExecutor asyncExecutor;

    /**
     * 系统用户服务
     */
    @Resource
    private SysUserService sysUserService;


    /**
     * 需要存储的用户列表
     */
    LinkedList<SysUser> sysUsers;


    /**
     * 批量插入的数据条数
     */
    Integer batchSize = 100000;

    /**
     * 多线程实现时的线程数
     */
    Integer threadSize = 10;

    /**
     * 初始化
     */
    @PostConstruct
    public void init() {
        sysUsers = new LinkedList<>();
        for (int i = 0; i < batchSize; i++) {
            SysUser sysUser = SysUser.builder().build();
            sysUsers.add(sysUser);
        }
    }


    /**
     * mybatis 实现批量插入：  批量插入: 100000条数据耗时：104389 （毫秒）
     */
    @ApiOperation("mybatis实现批量插入")
    @GetMapping("mybatis")
    public void batchForMybatis() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        sysUserService.saveBatch(sysUsers);
        stopWatch.stop();
        log.info("批量插入: {}条数据耗时：{} （毫秒）", batchSize, stopWatch.getTotalTimeMillis());
    }

    /**
     * 多线程实现批量插入: </br>
     * <ol>
     *     <li>批量插入: 100000条数据耗时：31474 （毫秒） (10线程)</li>
     *     <li>批量插入: 100000条数据耗时：35381 （毫秒）（20线程）</li>
     *     <li> 批量插入: 100000条数据耗时：45604 （毫秒）（5线程））</li>
     * </ol>
     */
    @ApiOperation("多线程实现批量插入(自带线程池，存在OOM问题)")
    @GetMapping("thread/v1")
    public void batchForThreads() {
        long startTime = System.currentTimeMillis();

        ExecutorService executorService = Executors.newFixedThreadPool(threadSize);
        for (int i = 0; i < threadSize; i++) {
            List<SysUser> usersByThread = sysUsers.subList(batchSize / threadSize * i,
                    batchSize / threadSize * (i + 1));

            Callable<Integer> task = () -> {
                sysUserService.saveBatch(usersByThread);
                return 1;
            };

            executorService.submit(task);
        }
        executorService.shutdown();
        while (true) {
            if (executorService.isTerminated()) {
                log.info("批量插入: {}条数据耗时：{} （毫秒）", batchSize, System.currentTimeMillis() - startTime);
                break;
            }
        }

    }

    /**
     * 线程v2批处理: 耗时：
     *
     * @return {@link Result}
     * @throws ExecutionException   执行异常
     * @throws InterruptedException 中断异常
     */
    @ApiOperation(value = "多线程实现批量插入,自定义线程池")
    @GetMapping("thread/v2")
    public Result<Boolean> batchForThreadV2() throws ExecutionException, InterruptedException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Future<Boolean> future = asyncExecutor.submit(() -> sysUserService.saveBatch(sysUsers));
        stopWatch.stop();
        return success(future.get(), String.valueOf(stopWatch.getTotalTimeMillis()));
    }
}
